package xworker.swt.actions;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Widget;
import org.xmeta.ActionContext;
import org.xmeta.Thing;
import org.xmeta.util.ActionContainer;
import org.xmeta.util.UtilMap;
import xworker.dataObject.*;
import xworker.lang.executor.Executor;
import xworker.swt.util.SwtUtils;

import java.util.*;

public class ActionsBinder {
    private static final String TAG = ActionsBinder.class.getName();

    public static void bind(Widget parent, ActionContainer actions, String binds, ActionContext actionContext){
        BinderListener listener = new BinderListener(actions);
        parent.addDisposeListener(listener);

        if(binds != null){
            for(String line : binds.split("[\n]")){
                line = line.trim();
                if(line.isEmpty() || line.startsWith("#") || line.startsWith("//")){
                    continue;
                }

                String[] ls = line.split(">");
                if(ls.length < 2){
                    Executor.warn(TAG, "Invalid syntax, " + line);
                    continue;
                }

                String actionName = ls[0].trim();
                String[] targets = ls[1].split("[:]");
                String varName = targets[0].trim();
                String eventName = targets[1].trim();

                Object obj = actionContext.getObject(varName);
                if(obj == null){
                    Executor.warn(TAG, "Can not bind, var is null, " + line);
                    continue;
                }

                if(obj instanceof Widget){
                    int type = SwtUtils.getSWT(eventName);
                    if(type == SWT.NONE){
                        Executor.warn(TAG, "Can not bind widget, event type not exists, " + line);
                        continue;
                    }

                    ((Widget) obj).addListener(type, event -> {
                        try{
                            actions.doAction(actionName, actionContext, "event", event);
                        }catch(Exception e){
                            Executor.warn(TAG, "Execute action " + actionName + " error", e);
                        }
                    });
                }else {
                    listener.map(actionName, eventName);
                    listener.addBindObj(obj);

                    if (obj instanceof DataObject) {
                        ((DataObject) obj).addListener(listener);
                    }else if(obj instanceof DataObjectList){
                        ((DataObjectList) obj).addListener(listener);
                    }else if(obj instanceof  DataStore){
                        ((DataStore) obj).addListener(listener);
                    }
                }
            }
        }
    }

    public static void create(ActionContext actionContext){
        Thing self = actionContext.getObject("self");

        ActionContainer actions = self.doAction("getActionContainer", actionContext);
        if(actions == null){
            Executor.warn(TAG, "Can not bind. ActionContainer is null, path=" + self.getMetadata().getPath());
            return;
        }

        Widget parent = actionContext.getObject("parent");
        String binds = self.doAction("getBinds", actionContext);

        bind(parent, actions, binds, actionContext);
    }

    public static class BinderListener implements DataObjectListener, DataStoreListener, DataObjectListListener, DisposeListener {
        ActionContainer actions;
        Map<String, List<String>> actionsMapping = new HashMap<>();
        List<Object> bindObjs = new ArrayList<>();

        public BinderListener(ActionContainer actions){
            this.actions = actions;
        }

        public void addBindObj(Object obj){
            if(obj != null && !bindObjs.contains(obj)){
                bindObjs.add(obj);
            }
        }

        public void map(String actionName, String eventName){
            List<String> actions = actionsMapping.computeIfAbsent(eventName, k -> new ArrayList<>());

            if(!actions.contains(actionName)){
                actions.add(actionName);
            }
        }

        public void invoke(String eventName, Object ... params){
            Map<String, Object> map = UtilMap.toMap(params);

            try{
                actions.doAction(eventName, map);
            }catch(Exception e){
                Executor.warn(TAG, "Invoke " + eventName + " exception", e);
            }
        }

        @Override
        public void changed(DataObject dataObject) {
            invoke("changed", "dataObject", dataObject);
        }

        @Override
        public void onReconfig(DataObjectList list) {
            invoke("onReconfig", "list", list);
        }

        @Override
        public void onLoaded(DataObjectList list) {
            invoke("onLoaded", "list", list);
        }

        @Override
        public void onAdded(DataObjectList list, DataObject dataObject) {
            invoke("onAdded", "list", list, "dataObject", dataObject);
        }

        @Override
        public void onAdded(DataObjectList list, int index, DataObject dataObject) {
            invoke("onAdded", "list", list, "dataObject", dataObject, "index", index);
        }

        @Override
        public void onAddedAll(DataObjectList list, int index, Collection<? extends DataObject> c) {
            invoke("onAddedAll", "list", list, "c", c, "index", index);
        }

        @Override
        public void onRemoved(DataObjectList list, int index, DataObject dataObject) {
            invoke("onRemoved", "list", list, "dataObject", dataObject, "index", index);
        }

        @Override
        public void onSeted(DataObjectList list, int index, DataObject newDataObject, DataObject oldDataObject) {
            invoke("onSeted", "list", list, "newDataObject", newDataObject, "index", index, "oldDataObject", oldDataObject);
        }

        @Override
        public void onReconfig(DataStore dataStore, Thing dataObject) {
            invoke("onReconfig", "dataStore", dataStore, "dataObject", dataObject);
        }

        @Override
        public void onLoaded(DataStore dataStore) {
            invoke("onLoaded", "dataStore", dataStore);
        }

        @Override
        public void onTotalCountUpdated(DataStore dataStore) {

        }

        @Override
        public void onChanged(DataStore dataStore) {
            invoke("onChanged", "dataStore", dataStore);
        }

        @Override
        public void beforeLoad(DataStore dataStore, Thing condition, Map<String, Object> params) {
            invoke("beforeLoad", "dataStore", dataStore, "condition", condition, "params", params);
        }

        @Override
        public void widgetDisposed(DisposeEvent e) {
            for(Object obj : bindObjs){
                if(obj instanceof DataObject){
                    ((DataObject) obj).removeListener(this);
                }else if(obj instanceof DataObjectList){
                    ((DataObjectList) obj).removeListener(this);
                }else if(obj instanceof DataStore){
                    ((DataStore) obj).removeListener(this);
                }
            }
        }
    }
}
